/*******************************************************************************
 * Copyright (c) 2010 liw.
 * All rights reserved. 
 * 
 * This file is part of VanBus.
 * 
 *     VanBus is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     VanBus is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with VanBus.  If not, see <http://www.gnu.org/licenses/>.
 * Contributors:
 *     liw - initial API and implementation
 ******************************************************************************/
package org.niclab.vanbus.reminder;

import org.niclab.vanbus.R;
import org.niclab.vanbus.activity.comp.EndStopReminderDialog;
import org.niclab.vanbus.activity.controller.BusStopController;
import org.niclab.vanbus.activity.controller.ReminderController;
import org.niclab.vanbus.application.VanBusApplication;
import org.niclab.vanbus.model.BusStop;
import org.niclab.vanbus.model.EndStopReminder;
import org.niclab.vanbus.query.DrivingDirectionQuery;
import org.niclab.vanbus.query.SimpleQueryWorker;
import org.niclab.vanbus.query.command.ICommand;
import org.niclab.vanbus.query.command.ICommandChain;
import org.niclab.vanbus.query.ws.GoogleDirectionWSQueryResResolver;
import org.niclab.vanbus.query.ws.SimpleWSQueryHandler;
import org.niclab.vanbus.query.ws.GoogleDirectionWSQueryResResolver.QueryResult;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.IBinder;
import android.util.Log;

/* If someone calls Context.startService() 
 * then the system will retrieve the service (creating it and calling its onCreate() method if needed) 
 * and then call its onStartCommand(Intent, int, int) method with the arguments supplied by the client. 
 * The service will at this point continue running until Context.stopService() or stopSelf() is called. 
 * Note that multiple calls to Context.startService() do not nest 
 * (though they do result in multiple corresponding calls to onStartCommand()), 
 * so no matter how many times it is started 
 * a service will be stopped once Context.stopService() or stopSelf() is called; 
 * however, services can use their stopSelf(int) method to ensure the service is not stopped 
 * until started intents have been processed.
 * 
 */
public class EndStopReminderService extends WakefulService {

	private static final String LOG_TAG="EndStopReminderService";
	private Context context;
	private LocationManager locManager;
	private SimpleQueryWorker worker;
		
	private EndStopReminder model;
	private BusStop destStop;
	private BusStop prevStop;
	
	private static final int END_STOP_NOTIFICATION =2;

	@Override
	public void onCreate() {
		context = getApplicationContext();
		locManager = (LocationManager) context
				.getSystemService(LOCATION_SERVICE);
		

	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		//wake lock acquired when the intent is received
		//WakefulService.acquireStaticLock(context);

		worker = new SimpleQueryWorker();
		
		
		// retrieve the model from the intent
		model = new EndStopReminder(intent.getBundleExtra(ReminderController.REMINDTER_END_STOP_LOCATION_UPDATE_INTENT_KEY_DATA),context);
		destStop = model.getDestStop();
		prevStop = model.getPrevStop();
		
		int intentSrc = intent.getIntExtra(ReminderController.REMINDER_END_STOP_LOCATION_UPDATE_INTENT_KEY_SOURCE, 0);
		if (intentSrc == ReminderController.REMINDER_END_STOP_INTENT_SRC_PROXIMITY_ALERT){
			preProcessProximityAlert(intent);
		}
		
		handleLocationUpdate(intent);
		
		registerCommands(intent);
		worker.work();
		
		
		return 0;
	}

	private void registerCommands(Intent intent){
		//hook up the commands
		NextLocUpdateCmd paCmd = new NextLocUpdateCmd(intent);
		ReleaseLockCmd rCmd = new ReleaseLockCmd();
		
		worker.addAsyncPostCommand(paCmd);
		worker.addAsyncPostCommand(rCmd);
		
	
	}
	
	private void preProcessProximityAlert(Intent intent) {
		
		// the distance from prev to destination
	

		DrivingDirectionQuery prevToDestDirQuery = new DrivingDirectionQuery(context);
		prevToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.START_LOC_LAT, prevStop.getLatitude());
		prevToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.START_LOC_LNG, prevStop.getLongitude());
		prevToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.END_LOC_LAT, destStop.getLatitude());
		prevToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.END_LOC_LNG, destStop.getLongitude());
		
		
		GoogleDirectionWSQueryResResolver prevToDestDirResHandler = new GoogleDirectionWSQueryResResolver();
		SimpleWSQueryHandler prevToDestDirQHandler = new SimpleWSQueryHandler(prevToDestDirQuery,prevToDestDirResHandler){
			@Override
			protected void onPostQuery(){
				GoogleDirectionWSQueryResResolver.QueryResult result =(QueryResult) this.getQuery().getQueryResult();
				model.setDistancePrevToDest(result.getDistance());
			}
			
		};


		worker.addAsyncQueryHandler(prevToDestDirQHandler);


	}

	private void handleLocationUpdate(Intent intent) {
			
		
		// the distance from current location to destination
		//Location loc = LocationUtils.getIntance(context).getCurrentGPSLocation(true);
		Location loc = VanBusApplication.getInstance().getMyLocationManager().getCurrentBestLocation(0);
		if (!VanBusApplication.getInstance().getMyLocationManager().isGPSEnabled()||loc==null) {
			
			sendNotification("Stop Reminder Canceled!", "Your bus stop reminder is cancelled !", "no GPS provider is available. Reminder Canceled !",false);
			return;
		}


		DrivingDirectionQuery curToDestDirQuery = new DrivingDirectionQuery(context);
		curToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.START_LOC_LAT, loc.getLatitude());
		curToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.START_LOC_LNG, loc.getLongitude());
		curToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.END_LOC_LAT, destStop.getLatitude());
		curToDestDirQuery.addQueryInputParam(DrivingDirectionQuery.InputParams.END_LOC_LNG, destStop.getLongitude());
		
		GoogleDirectionWSQueryResResolver curToDestDirResHandler = new GoogleDirectionWSQueryResResolver();
		SimpleWSQueryHandler curToDestDirQHandler = new SimpleWSQueryHandler(curToDestDirQuery,curToDestDirResHandler){

			@Override
			protected void onPostQuery(){
				GoogleDirectionWSQueryResResolver.QueryResult result =(QueryResult) this.getQuery().getQueryResult();
				model.setDistanceToDest(result.getDistance());
			}
		};


		worker.addAsyncQueryHandler(curToDestDirQHandler);
		

	}

	@Override
	public IBinder onBind(Intent arg0) {
		
		return null;
	}
	

	
	private void sendNotification(String tickerText, String title, String content, boolean success) {
		NotificationManager notifyManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
		
		int icon = R.drawable.bus;
		long when = System.currentTimeMillis();         // notification time
		
		
		Notification notification = new Notification(icon, tickerText,when);
		notification.defaults |=Notification.DEFAULT_VIBRATE;
		notification.defaults |=Notification.DEFAULT_SOUND;
		
		notification.flags |= notification.FLAG_INSISTENT;
		
		Intent notificationIntent = new Intent();
		PendingIntent pIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_ONE_SHOT);
		
		notification.setLatestEventInfo(context, title, content, pIntent);

		notifyManager.notify(EndStopReminderService.END_STOP_NOTIFICATION, notification);


	}


	private class NextLocUpdateCmd implements ICommand {
		
		private Intent intent;
		public NextLocUpdateCmd(Intent intent){
			this.intent=intent;
		}


		@Override
		public void execute(Object input, ICommandChain chain) {
			/*
			 * if curToDest distance is smaller than prevToDest distance {
			 * 		if(curToDest distance is less than 300m ){ 
			 * 			we can send the notification to user 
			 * 		}
			 * 		else 
			 * 			we decide the next update interval as the half of the distance to go and half of the time to go 
			 * 			we register a location update request with a intent.
			 * }
			 * else{
			 * 		we register a location update request with an intent
			 * }
			 */
			//
			float min_bus_speed = Float.parseFloat(context
					.getString(R.string.reminder_configs_min_bus_speed));
			float speed = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
					.getSpeed();
			speed = (speed < min_bus_speed) ? min_bus_speed : speed;

			long locUpdateInterval;
			float locUpdateDistance;
			Log.d(LOG_TAG, "current to dest distance: " + model.getDistanceToDest());
			Log.d(LOG_TAG, "prev to dest distance: " + model.getDistancePrevToDest());
			if (model.getDistanceToDest() < model.getDistancePrevToDest()) {
				// next stop could be the destination

				if(model.getDistanceToDest() < model.getDistancePrevToDest()*2/3){
				//if (model.getDistanceToDest() < Float.parseFloat(context.getString(R.string.reminder_end_stop_notification_distance_threshold))) {
					sendNotification("Your Destination Stop","Next Stop is your Stop !","You can get off at the next bus stop!",true );
					chain.execute(input);
					return;
				} 
			}
			//register a location update
			
			float timeToGo = model.getDistanceToDest()*1000 / speed; //milliseconds

			locUpdateInterval = (long) (timeToGo / 2);
			locUpdateDistance = model.getDistanceToDest() / 2;

			
			PendingIntent pIntent = new PendingIntentBuilder()
							.setActionName(intent.getAction())
							.setData(intent.getData())
							.setIntentSource(ReminderController.REMINDER_END_STOP_INTENT_SRC_GPS_UPDATE)
							.setModel(model).build();

			
			Log.d(LOG_TAG, "PendingIntent to add: hashcode " + pIntent.hashCode());
			Log.d(LOG_TAG, "PendingIntent to add: " + pIntent);
			Log.d(LOG_TAG, "next location update interval: " + locUpdateInterval);
			Log.d(LOG_TAG, "next location update distance: " + locUpdateDistance);
			locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, locUpdateInterval, locUpdateDistance, pIntent);
						
			chain.execute(input);
		}

		
	}

	private class ReleaseLockCmd implements ICommand {

		@Override
		public void execute(Object input, ICommandChain chain) {
			getLock(context).release();
			chain.execute(input);

		}

	}

	protected class PendingIntentBuilder {
		private EndStopReminder model;
		private String actionName;
		private int intentSource;
		private Intent intent;
		private Uri uri;

		public PendingIntentBuilder setModel(EndStopReminder model) {
			this.model = model;
			return this;
		}

		public PendingIntentBuilder setActionName(String action) {
			this.actionName = action;
			return this;
		}

		public PendingIntentBuilder setIntentSource(int source) {
			this.intentSource = source;
			return this;
		}
		
		public PendingIntentBuilder setIntent(Intent intent){
			this.intent=intent;
			return this;
		}
		
		public PendingIntentBuilder setData(Uri uri){
			this.uri = uri;
			return this;
			
		}

		public PendingIntent build() {
			if(intent!=null)
			{
				intent.putExtra(ReminderController.REMINDTER_END_STOP_LOCATION_UPDATE_INTENT_KEY_DATA, model.createBundle());
				PendingIntent pIntent = PendingIntent.getBroadcast(context, 0,
						intent, 0);
				return pIntent;
			}
			Intent intent = new Intent(actionName);
			intent.setData(uri);
			intent.putExtra(ReminderController.REMINDER_END_STOP_LOCATION_UPDATE_INTENT_KEY_SOURCE, intentSource);
			intent.putExtra(ReminderController.REMINDTER_END_STOP_LOCATION_UPDATE_INTENT_KEY_DATA, model.createBundle());
			PendingIntent pIntent = PendingIntent.getBroadcast(context, 0,
					intent, PendingIntent.FLAG_UPDATE_CURRENT);
			return pIntent;
		}

	}

}
